use std::collections::HashMap;
use crate::sym_type::*;

#[derive(Clone)]
pub struct Symbol {
    pub label: String,
    pub sym_type: SymType,
    pub sym_val: SymVal,
}

pub enum ScopeType {
    BasicBlock,
    If,
    While(String, String),
    Func(SymType),
    Global,
}

pub struct Scope {
    pub ty: ScopeType,
    pub tab: HashMap<String, Symbol>,
}

impl Scope {
    fn new() -> Self {
        Scope{
            ty: ScopeType::Global,
            tab: HashMap::new(),
        }
    }
}

pub struct Scopes {
    pub scope_vec: Vec<Scope>,
}

impl Scopes {
    pub fn new() -> Self {
        Scopes {
            scope_vec: vec!(Scope::new()),
        }
    }

    pub fn is_global(&self) -> bool {
        self.scope_vec.len() == 1
    }

    pub fn push(&mut self, labels: &mut Labels, ident: &str, sym_type: &SymType, sym_val: &SymVal, flag: Option<bool>) -> Option<String> {
        let flag = flag.unwrap_or(self.is_global());
        let curr_scope = self.scope_vec.last_mut().unwrap();

        let label: String;
        if flag {
            label = labels.pop_global(ident);
        } else {
            label = labels.pop_local(ident);
        }
        match curr_scope.tab.insert(
            String::from(ident),
            Symbol {
                label: String::from(&label),
                sym_type: sym_type.clone(),
                sym_val: sym_val.clone(),
        }) {

            Some(_) => None, // 已经存在
            None => Some(label),
        }
    }

    pub fn enter_func(&mut self, func_type: &SymType) {
        self.scope_vec.push(Scope{
          ty: ScopeType::Func(func_type.clone()),
          tab: HashMap::new(),
        });
    }

    pub fn enter_block(&mut self) {
        self.scope_vec.push(Scope{
          ty: ScopeType::BasicBlock,
          tab: HashMap::new(),
        });
    }

    pub fn enter_if(&mut self) {
        self.scope_vec.push(Scope{
          ty: ScopeType::If,
          tab: HashMap::new(),
        });
    }

    pub fn enter_while(&mut self, entry: &String, end: &String) {
        self.scope_vec.push(Scope{
          ty: ScopeType::While(String::from(entry), String::from(end)),
          tab: HashMap::new(),
        });
    }

    pub fn exit(&mut self) {
        self.scope_vec.pop();
    }

    pub fn is_inside_while(&self) -> bool {
        for scope in self.scope_vec.iter().rev() {
            if let ScopeType::While(_, _) = &scope.ty {
                return true;
            }
        }
        false
    }

    pub fn get_while_entry(&mut self) -> Option<String> {
        for scope in self.scope_vec.iter().rev() {
            if let ScopeType::While(entry, _) = &scope.ty {
                return Some(String::from(entry));
            }
        }
        None
    }

    pub fn get_while_end(&mut self) -> Option<String> {
        for scope in self.scope_vec.iter().rev() {
            if let ScopeType::While(_, end) = &scope.ty {
                return Some(String::from(end));
            }
        }
        None
    }

    pub fn get_curr_func_type(&self) -> Option<SymType> {
        for scope in self.scope_vec.iter().rev() {
            if let ScopeType::Func(ty) = &scope.ty {
                return Some(ty.clone());
            }
        }
        None
    }

    pub fn get_func(&self, id: &str) -> Option<&Symbol> {
        for scope in self.scope_vec.iter().rev() {
            if let Some(item) = scope.tab.get(id) {
                match item.sym_val {
                    SymVal::Func(_, _) => return scope.tab.get(id),
                    _ => {},
                }
            }
        }
        None
    }

    pub fn get(&mut self, id: &str) -> Option<&mut Symbol> {
        for scope in self.scope_vec.iter_mut().rev() {
            if scope.tab.get(id).is_some() {
                return scope.tab.get_mut(id);
            }
        }
        None
    }

    pub fn get_depth(&self) -> usize {
        self.scope_vec.iter()
            .filter(|scope| {
                if let ScopeType::While(_, _) = scope.ty {
                    true
                } else {
                    false
                }
            })
            .count()
    }
}

pub struct Labels {
    pub counter: i32,                           // pure number counter
    pub local: HashMap<String, i32>,            // local label counter
    pub global: HashMap<String, i32>,           // global label counter
    pub block: HashMap<String, i32>,            // 基本块标号计数，只允许特定标号
}

const BLOCK_LABELS: [&str; 14] = [
    "_L", "ret_then",
    "or_false", "or_end",
    "and_true", "and_end",
    "break_then", "continue_then",
    "if_then", "if_else", "if_end",
    "while_entry", "while_body", "while_end",
];

impl Labels {
    pub fn new() -> Self {
        let mut labels = Labels {
            counter: 0,
            local: HashMap::new(),
            block: HashMap::new(),
            global: HashMap::new(),
        };

        for label in BLOCK_LABELS.iter() {
            labels.block.insert(String::from(*label), 0);  
        }
        labels
    }

    pub fn pop_num_str(&mut self) -> String {
        let res = format!("%{}", self.counter);
        self.counter += 1;
        res
    }

    pub fn recover_num(&mut self) {
        self.counter -= 1;
    }
    
    pub fn pop_local(&mut self, ident: &str) -> String {
        let val: Option<&mut i32>;
        let mut id = ident;
        if ident.len() > 15 {
            id = &id[..15];
            val = self.local.get_mut(id);
        } else {
            val = self.local.get_mut(id);
        }
        match val {
            Some(cnt) => {
                let res = format!("%{}_{}", id, cnt);
                *cnt += 1;
                res
            },
            None => {
                self.local.insert(String::from(id), 1);
                format!("%{}_0", id)
            },
        }
    }

    pub fn pop_global(&mut self, ident: &str) -> String {
        let val: Option<&mut i32>;
        let mut id = ident;
        if ident.len() > 15 {
            id = &id[..15];
            val = self.global.get_mut(id);
        } else {
            val = self.local.get_mut(id);
        }

        match val {
            Some(cnt) => {
                let res = format!("@{}_{}", id, cnt);
                *cnt += 1;
                res
            },
            None => {
                self.local.insert(String::from(id), 1);
                format!("@{}", id)
            },
        }
    }
    
    pub fn pop_block(&mut self, ident: &str) -> String {
        let val = self.block.get_mut(ident);
        match val {
            Some(cnt) => {
                let res = format!("{}_{}", ident, cnt);
                *cnt += 1;
                res
            },
            None => panic!("Undefined block label")
        }
    }
    
    pub fn clear(&mut self) {
        self.local = HashMap::new();
        self.counter = 0;
        for (_, counter) in self.block.iter_mut() {
            *counter = 0;
        }
    }
}

